Skip to content

fix(workflow): modified PR Percy workflow for better security#673

Merged
ArtBlue merged 3 commits into
mainfrom
fix-pr-workflow
May 15, 2026
Merged

fix(workflow): modified PR Percy workflow for better security#673
ArtBlue merged 3 commits into
mainfrom
fix-pr-workflow

Conversation

@ArtBlue
Copy link
Copy Markdown
Contributor

@ArtBlue ArtBlue commented May 12, 2026

Summary

Replaces the pull_request_target approach with a secure two-stage workflow that enables Percy visual regression snapshots for fork PRs without exposing secrets to untrusted code.

Background

Percy snapshots were silently failing for all external contributor PRs because GitHub withholds repository secrets from pull_request workflows triggered by forks — PERCY_TOKEN was always an empty string for fork PRs.

A first fix using pull_request_target was implemented, but due to a recent exploit of this pattern: fork code running in a privileged context poisoned the npm cache, which was later restored by a release workflow with id-token: write, enabling unauthorized npm publishes. Given that our ci.yml release job also has id-token: write, the risk profile was unacceptable.

Solution

Split Percy CI into two isolated workflow stages:

Stage 1 — percy-build.yml (triggered by pull_request, no secrets)

  • Checks out the PR code via the standard pull_request event — no elevated privileges
  • Detects which Skin components changed via the existing detect-changed-components action
  • Builds a static Storybook from the PR's code
  • Saves Percy metadata (component list, PR number) alongside the build
  • Uploads everything as a short-lived GitHub Actions artifact

Stage 2 — percy.yml (triggered by workflow_run, has secrets)

  • Only runs when Stage 1 completes successfully — cancelled or failed builds are skipped
  • Checks out the base branch and installs dependencies from there — no fork code executes at this stage
  • Posts a pending commit status to the PR immediately so the check is visible while Percy runs
  • Downloads the pre-built Storybook artifact from Stage 1
  • Reads the saved metadata; commit SHA and branch are sourced directly from the workflow_run event payload for reliability
  • Runs percy storybook against the static artifact, filtered to changed components when applicable
  • Posts a final success or failure commit status back to the PR regardless of outcome (if: always())
  • PERCY_TOKEN only ever exists in this stage, in a fully trusted context

The main branch baseline job is unchanged — it continues to run snapshots:all on push to main.

Security Properties

  • Fork code runs in Stage 1 with no secrets and no privileged context
  • PERCY_TOKEN is fully isolated to Stage 2, which runs entirely from base-branch code
  • Eliminates the cache poisoning vector that enabled the TanStack attack: Stage 1 and Stage 2 run in separate jobs with separate npm installs, so a poisoned cache from a fork PR cannot reach the privileged stage

Trade-offs

  • Storybook is always built in full in Stage 1, with Percy filtering to changed components in Stage 2 via --include — slightly slower than the previous partial-build approach but required for the isolation model
  • Percy status checks appear with a short delay after Stage 1 completes rather than inline with other PR checks

@ArtBlue ArtBlue self-assigned this May 12, 2026
Copilot AI review requested due to automatic review settings May 12, 2026 18:07
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

⚠️ No Changeset found

Latest commit: b92b7e9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Percy visual regression for forked pull requests by replacing the insecure pull_request_target pattern with a two-stage workflow: an unprivileged PR build that produces a static Storybook artifact, followed by a privileged workflow_run job that runs Percy using secrets without executing fork code.

Changes:

  • Add a new percy-build.yml workflow to build Storybook on pull_request and upload it (plus metadata) as a short-lived artifact.
  • Update percy.yml to run PR Percy snapshots on workflow_run success, download the artifact, and post commit statuses.
  • Keep the push-to-main baseline Percy job in percy.yml.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
.github/workflows/percy.yml Switch PR Percy execution to workflow_run, download Stage 1 artifact, run Percy from trusted code, and report commit status.
.github/workflows/percy-build.yml New Stage 1 PR workflow that builds static Storybook and uploads it as an artifact with component/PR metadata.
Comments suppressed due to low confidence (1)

.github/workflows/percy.yml:20

  • The workflow grants pull-requests: write and checks: write, but this job only uses the Statuses API (repos.createCommitStatus) and artifact download. Reducing permissions to the minimum needed (e.g., contents: read, statuses: write, actions: read) lowers risk, especially since this workflow is explicitly security-motivated.
permissions:
  contents: read
  pull-requests: write
  checks: write
  statuses: write

Comment thread .github/workflows/percy.yml
Comment thread .github/workflows/percy.yml Outdated
Comment thread .github/workflows/percy.yml Outdated
Comment thread .github/workflows/percy.yml
Comment thread .github/workflows/percy-build.yml
@ArtBlue
Copy link
Copy Markdown
Contributor Author

ArtBlue commented May 15, 2026

Note: the dual Percy run is an unavoidable artifact of switching the Percy workflow from the build-layer run to the workflow-layer run.

@ArtBlue ArtBlue merged commit f0be74b into main May 15, 2026
6 of 7 checks passed
@ArtBlue ArtBlue deleted the fix-pr-workflow branch May 15, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants